#pragma once

#include "deviceManager.hpp"
#include "msgObj.hpp"
#include <any>
#include <optional>

namespace Protocol {
class DevReadMsgObject : public MsgObject {
public:
    DevReadMsgObject(std::shared_ptr<MqttClient> m, std::shared_ptr<DeviceManager> d)
        : MsgObject(m, d) {}

private:
    std::size_t tick;
    std::size_t time;
    std::string src;
    std::string dest;

    struct Ack {
        std::size_t tick;
        std::size_t time;
        friend void to_json(nlohmann::json &j, const Ack &a) { j = {{"tick", a.tick}, {"time", a.time}}; }
        Ack &setTickAndTime(std::size_t tick, std::size_t time) {
            this->tick = tick;
            this->time = time;
            return *this;
        }
    } ack;

    struct Dev {
        std::optional<std::size_t> id;
        std::optional<std::string> model;

        std::string status;
        struct Point {
            std::string key;
            std::any val;
            std::size_t time;
            std::size_t err;

            Point()
                : err(0) {}

            Point &setError(bool stat) {
                err = stat ? 0 : 1;
                return *this;
            }

            Point &setValue(const std::any &v) {
                val = v;
                return *this;
            }

            friend void from_json(const nlohmann::json &j, Point &p) {
                if (j.contains("key")) {
                    j.at("key").get_to(p.key);
                }
            }

            friend void to_json(nlohmann::json &j, const Point &p) {
                j = {
                    {"key", p.key},
                    {"val", any_to_json(p.val)},
                    {"time", std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count()},
                };

                if (p.err) {
                    j["err"] = p.err;
                }
            }
        };
        std::vector<Point> points;

        Dev &setStatus(bool stat) {
            status = stat ? "online" : "offline";
            return *this;
        }

        friend void from_json(const nlohmann::json &j, Dev &d) {
            if (j.contains("id")) {
                d.id = j.at("id").get<std::size_t>();
            }

            if (j.contains("model")) {
                d.model = j.at("model").get<std::string>();
            }

            if (j.contains("points") && j.at("points").is_array()) {
                for (const auto &point : j.at("points")) {
                    d.points.emplace_back(point);
                }
            }
        }

        friend void to_json(nlohmann::json &j, const Dev &d) {
            j = {{"status", d.status}};

            if (d.id.has_value()) {
                j["id"] = d.id.value();
            } else if (d.model.has_value()) {
                j["model"] = d.model.value();
            }

            for (const auto &point : d.points) {
                j["points"].emplace_back(point);
            }
        }

        bool isReadById(void) const { return id.has_value(); }
        bool isReadByModel(void) const { return model.has_value(); }
    };

    std::vector<Dev> dev;

public:
    friend void from_json(const nlohmann::json &j, DevReadMsgObject &d) {
        if (j.contains("tick")) {
            j.at("tick").get_to(d.tick);
        }

        if (j.contains("time")) {
            j.at("time").get_to(d.time);
        }

        if (j.contains("src")) {
            j.at("src").get_to(d.src);
        }

        if (j.contains("dev") && j.at("dev").is_array()) {
            for (const auto &dev : j.at("dev")) {
                d.dev.emplace_back(dev);
            }
        }
    }
    friend void to_json(nlohmann::json &j, const DevReadMsgObject &d) {
        j = {{"tick", d.tick}, {"time", d.time}, {"src", d.src}, {"ack", d.ack}};
        for (const auto &dev : d.dev) {
            j["dev"].emplace_back(dev);
        }
    }

public:
    virtual bool parse(const std::string &t, const std::string &p) override {
        auto dest = subTopic(t, 4);
        if ((isDisit(dest) && !_device->findDevice(std::stoi(dest))) || (!isDisit(dest) && dest != selfSrc)) {
            return false;
        }

        this->dest = dest;

        if (!nlohmann::json::accept(p)) {
            std::cerr << "not is json" << std::endl;
            return false;
        }

        nlohmann::json::parse(p).get_to(*this);

        return true;
    }
    virtual MsgObject &action(void) override {
        for (auto &d : dev) {
            if (d.isReadById()) {
                auto device = _device->findDevice(d.id.value());
                if (!device) {
                    std::cerr << "can't find device, device id --> " << d.id.value() << std::endl;
                    d.setStatus(false);
                    continue;
                }

                for (auto &p : d.points) {
                    if (device->hasPoint(p.key) && device->point(p.key)->hasData()) {
                        p.setError(true).setValue(device->pointValue(p.key));
                    } else if (device->hasPoint(p.key) && !device->point(p.key)->hasData()) {
                        p.setError(device->readPoint(p.key)).setValue(device->pointValue(p.key));
                    } else {
                        p.setError(false).setValue(device->pointValue(p.key));
                    }
                }

                d.setStatus(true);
            }
        }

        return *this;
    }
    virtual bool reply(void) override {
        ack.setTickAndTime(tick, time);
        auto topic = "v1/dev/" + dest + "/" + src + "/point/get_ack";
        src = selfSrc;

        return _mqtt->publish(topic, nlohmann::json(*this).dump());
    }
};

};  // namespace Protocol
